﻿using InfluxData.Net.Common.Enums;
using InfluxData.Net.InfluxDb;
using InfluxData.Net.InfluxDb.Models;
using InfluxData.Net.InfluxDb.Models.Responses;
using Mohe.Scada.Config;
using Mohe.Scada.Util.Extensions;
using Mohe.Scada.Util.Helper;
using Mohe.Scada.Util.Models;
using Mohe.Scada.Util.TimeSeries;
using NLog;
using System.Text;

namespace Mohe.Scada.Common.TimeSeries
{
    public delegate void ExceptionHandle(Exception ex);
    public class InfluxDbStorage : IStorage
    {
        private static string _TelemetryDataDB = "TelemetryData";
        private static string _MunkReportDataDB = "MunkReport";
        private static string _TemperatureDataDB = "TemperatureData";
        private static DateTime beginTime = new DateTime(1970, 1, 1, 0, 0, 0);
        private static string dbName;
        private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();

        InfluxDbClient _client;
        public InfluxDbStorage()
        {
            dbName = LocalSetting.TsSetting.DataBase;

            _client = new InfluxDbClient(
                LocalSetting.TsSetting.Address,
                LocalSetting.TsSetting.UserName,
                LocalSetting.TsSetting.Password,
                InfluxDbVersion.Latest
                );

            var databasesResponse = _client.Database.GetDatabasesAsync();
            var dbs = databasesResponse.Result;
            bool exists = false;
            foreach (var db in dbs)
            {
                if (db.Name.Trim() == dbName)
                {
                    exists = true;
                    break;
                }

            }
            //  如果不存在此数据库，则进行创建，否则不创建
            if (!exists)
            {
                var createResponse = _client.Database.CreateDatabaseAsync(dbName).Result;
            }
        }

        public event ExceptionHandle InfluxException;
        private void DisplayException(Exception ex)
        {
            if (InfluxException != null)
            {
                InfluxException(ex);
            }
        }


        /// <summary>
        /// 创建一个数据表，根据设备
        /// </summary>
        /// <param name="device"></param>
        /// <returns></returns>
        private Point CreateDevicePoint(PayLoad payLoad)
        {
            try
            {
                Point point = new Point();
                point.Fields = CreateDeviceFields(payLoad);
                point.Timestamp = payLoad.LastUpdateTime;
                point.Name = $"{_TelemetryDataDB}_{LocalSetting.Tenant}";
                return point;
            }
            catch
            {
                return null;
            }
        }
        private Dictionary<string, object> CreateDeviceFields(PayLoad payLoad)
        {
            Dictionary<string, object> dict = new Dictionary<string, object>();
            try
            {
                dict.Add("UpdateTime", payLoad.LastUpdateTime);
                foreach (var t in payLoad.Values)
                {
                    dict[t.Key] = t.Value.ToString();
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                DisplayException(ex);
            }
            return dict;
        }
        public DateTime LongToDateTime(long val)
        {
            int digits = (int)(Math.Floor(Math.Log10(val)) + 1);
            if (digits == 13)
            {
                return beginTime.AddMilliseconds(val);
            }
            else if (digits == 10)
            {
                return beginTime.AddSeconds(val);
            }
            int div = 1 << digits - 10;
            val = val / div;
            return beginTime.AddSeconds(val);

        }
        public async Task<List<MunkReportData>> LoadMunkReportData(int StationNo, int SerialNum)
        {
            try
            {

                var strSql = new StringBuilder($"select * from {_MunkReportDataDB}_{LocalSetting.Tenant} where 1=1");
                if (SerialNum > 0)
                {
                    strSql.Append($" and SerialNum = '{SerialNum}'");
                }
                if (StationNo > 0)
                {
                    strSql.Append($" and StationNo = '{StationNo}'");
                }
                var resp = await _client.Client.QueryAsync(strSql.ToString(), dbName);
                var lst = new List<MunkReportData>();
                foreach (var item in resp)
                {
                    foreach (var val in item.Values)
                    {
                        var entity = new MunkReportData();
                        for (int i = 1; i < item.Columns.Count; i++)
                        {
                            if (val[i] == null)
                            {
                                continue;
                            }
                            switch (item.Columns[i])
                            {
                                case "StationNo":
                                    entity.StationNo = val[i].ToInt();
                                    break;
                                case "SerialNum":
                                    entity.SerialNum = val[i].ToInt();
                                    break;
                                case "CurActual":
                                    entity.CurActual = val[i].ToFloat();
                                    break;
                                case "VolActual":
                                    entity.VolActual = val[i].ToFloat();
                                    break;
                                case "SetCurr":
                                    entity.SetCurr = val[i].ToFloat();
                                    break;
                                case "SetVol":
                                    entity.SetVol = val[i].ToFloat();
                                    break;
                                case "ReportDate":
                                    entity.ReportDate = LongToDateTime((long)val[i]);
                                    break;
                            }
                        }
                        lst.Add(entity);
                    }
                }
                return lst;
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                DisplayException(ex);
            }
            return null;
        }



        public Task<List<TelemetryData>> LoadTelemetryAsync(string keys, DateTime begin, DateTime end, TimeSpan every, EnumAggregate aggregate)
        {
            var kvs = keys.Split(';', ',');
            var strSql = new StringBuilder("select ");
            foreach (var key in kvs)
            {
                if (aggregate != EnumAggregate.None)
                {
                    strSql.Append(aggregate.ToString() + "(");
                }
                strSql.Append($"\"{key}\"");
                if (aggregate != EnumAggregate.None)
                {
                    strSql.Append(")");
                }
                strSql.Append(",");
            }
            strSql.Remove(strSql.Length - 1, 1);
            strSql.Append($" from {_TelemetryDataDB}_{LocalSetting.Tenant} where  time>='{begin.ToUtcDateStr()}' and time <= '{end.ToUtcDateStr()}' ");
            if (every != TimeSpan.Zero)
            {
                strSql.Append($" group by time({every.TotalMilliseconds}ms) fill(linear)");
            }

            Logger.Debug(strSql.ToString());
            var res = _client.Client.QueryAsync(strSql.ToString(), dbName);
            return ToDtoAsync(res);
        }
        private async Task<List<TelemetryData>> ToDtoAsync(Task<IEnumerable<Serie>> v)
        {
            List<TelemetryData> dt = new List<TelemetryData>();
            var list = (await v);
            foreach (var item in list)
            {
                foreach (var val in item.Values)
                {
                    for (int i = 1; i < item.Columns.Count; i++)
                    {
                        if (val[i] != null)
                        {
                            dt.Add(new TelemetryData
                            {
                                Timestamp = val[0].ToLocalDate(),
                                VarName = item.Columns[i],
                                Value = val[i]
                            });
                        }
                    }
                }
            }
            return dt;
        }

        private async Task<List<TemperatureData>> GetTemperature(string strSql, int pageSize, int pageIndex)
        {
            var query = "select * " + strSql + $" ORDER BY time DESC   LIMIT {pageSize} OFFSET {(pageIndex - 1) * pageSize} ";
            var resp = await _client.Client.QueryAsync(query, dbName);
            var lst = new List<TemperatureData>();
            foreach (var item in resp)
            {
                foreach (var val in item.Values)
                {
                    var entity = new TemperatureData();
                    for (int i = 1; i < item.Columns.Count; i++)
                    {
                        if (val[i] == null)
                        {
                            continue;
                        }
                        switch (item.Columns[i])
                        {
                            case "StationNo":
                                entity.StationNo = val[i].ToInt();
                                break;
                            case "SerialNum":
                                entity.SerialNum = val[i].ToInt();
                                break;
                            case "SetTemp":
                                entity.SetTemp = val[i].ToFloat();
                                break;
                            case "TempActual":
                                entity.TempActual = val[i].ToFloat();
                                break;
                            case "ReportDate":
                                entity.ReportDate = LongToDateTime((long)val[i]);
                                break;
                        }
                    }
                    lst.Add(entity);
                }
            }
            return lst;
        }
        public async Task<List<TemperatureData>> LoadTemperatureAsync(int StationNo, int SerialNum = 0, int pageSize = 1000, int pageIndex = 1)
        {
            var strSql = new StringBuilder($" from {_TemperatureDataDB}_{LocalSetting.Tenant} where 1=1");
            if (SerialNum > 0)
            {
                strSql.Append($" and SerialNum = '{SerialNum}'");
            }
            if (StationNo > 0)
            {
                strSql.Append($" and StationNo = '{StationNo}'");
            }

            return await GetTemperature(strSql.ToString(), pageSize, pageIndex);

        }

        public async Task<List<TemperatureData>> LoadTemperatureAsync(string beginTime, string endTime, int StationNo, int pageSize = 1000, int pageIndex = 1)
        {
            var strSql = new StringBuilder($" from {_TemperatureDataDB}_{LocalSetting.Tenant} where 1=1");
            if (StationNo > 0)
            {
                strSql.Append($" and StationNo = '{StationNo}'");
            }
            if (!string.IsNullOrEmpty(beginTime))
            {
                strSql.Append($" and time >= '{beginTime}'");
            }
            if (!string.IsNullOrEmpty(endTime))
            {
                strSql.Append($" and time <= '{endTime}'");
            }
            return await GetTemperature(strSql.ToString(), pageSize, pageIndex);
        }

        public async Task<bool> StoreMunkReportAsync(List<MunkReportData> data)
        {
            try
            {
                List<Point> lst = new List<Point>();
                data.ForEach(kp =>
                {
                    Point point = new Point();
                    point.Name = $"{_MunkReportDataDB}_{LocalSetting.Tenant}";
                    point.Tags.Add("SerialNum", kp.SerialNum);
                    point.Tags.Add("StationNo", kp.StationNo);
                    point.Fields.Add("SetCurr", kp.SetCurr);
                    point.Fields.Add("SetVol", kp.SetVol);
                    point.Fields.Add("CurActual", kp.CurActual);
                    point.Fields.Add("VolActual", kp.VolActual);
                    point.Fields.Add("ReportDate", kp.ReportDate);
                    point.Timestamp = kp.ReportDate;
                    lst.Add(point);
                });
                var writeResponse = await _client.Client.WriteAsync(lst, dbName);

            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                DisplayException(ex);
                return false;
            }
            return true;
        }



        public async Task<bool> StoreTelemetryAsync(PayLoad payLoad)
        {
            try
            {
                var points = CreateDevicePoint(payLoad);
                await _client.Client.WriteAsync(points, dbName, null, "ms");

            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                DisplayException(new Exception(ex.Message));
                return false;
            }
            return true;
        }

        public async Task<bool> StoreTemperatureAsync(List<TemperatureData> data)
        {
            try
            {
                List<Point> lst = new List<Point>();
                data.ForEach(kp =>
                {
                    Point point = new Point();
                    point.Name = $"{_TemperatureDataDB}_{LocalSetting.Tenant}";
                    point.Tags.Add("SerialNum", kp.SerialNum);
                    point.Tags.Add("StationNo", kp.StationNo);
                    point.Fields.Add("TempActual", kp.TempActual);
                    point.Fields.Add("SetTemp", kp.SetTemp);
                    point.Fields.Add("ReportDate", kp.ReportDate);
                    point.Timestamp = kp.ReportDate;
                    lst.Add(point);
                });
                await _client.Client.WriteAsync(lst, dbName);
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                DisplayException(ex);
                return false;
            }
            return true;
        }
    }
}
